It seems like PERMANOVA is pretty good at doing discriminant analyses. When is PLS-DA better, if ever?

To-Do:

Setup

Load packages

# Required Packages
library(MASS)
library(tidyverse)
library(ropls)
library(chemhelper)
#chemhelper contains custom functions for interfacing with ropls package in a friendlier way (and other things).  
#Install with devtools::install_github("Aariq/chemhelper")
library(cowplot) #for making and saving prettier plots
library(broom) #for tidy(), which turns model output into dataframes
library(vegan) #for PERMANOVA

Create Function to Generate Data

This code chunk creates the function sim.vcov() which generates a dataset where we can tune signal, noise, discriminating variables, strength of signal, and strength of discriminating variables. I’m using the notation n for number of observations and p for number of variables. sim.vcov() requires the following arguments:

  • p_noise: how many variables with covariance = 0.01?
  • p_signal: how many variables with some covariance (covariance = cov_signal)?
  • p_disc: how many discriminating variables?
  • cov_signal: the covariance for the variables with non-zero covariance.
  • diff_disc: the difference in means for the discriminating variables between the two groups.
  • N: how many observations?
  • seed: passed to set.seed()
# p_noise = 0
# p_signal = 5
# p_disc = 15
# cov_signal = 0.5
# diff_disc = 1
# cov_disc = 0.5
# group_vars = c(1,2)
# N = 20
# seed = NA
sim.vcov <- function(p_noise,
                     p_signal,
                     p_disc,
                     cov_signal,
                     cov_disc,
                     diff_disc,
                     group_vars = c(1, 1),
                     N,
                     seed = NA){
  # Choose a random number for seed if none is supplied
  if(is.na(seed)){
    seed = as.integer(runif(1) * 10e6)
  }
  
  # make noise data
  if(p_noise > 0){
  S_noise <- matrix(rep(0.001, p_noise^2), ncol = p_noise)
  diag(S_noise) <- 1
  set.seed(seed)
  data_noise <- mvrnorm(n = N, Sigma = S_noise, mu = rep(0, p_noise)) %>%
    as.data.frame()
  colnames(data_noise) <- paste0("noise_", 1:p_noise)
  } else {data_noise <- list()}
  
  # make signal data
  if(p_signal > 0){
    S_signal <- matrix(rep(cov_signal, p_signal^2), ncol = p_signal)
    S_signal_B <- S_signal_A <- S_signal
    diag(S_signal_A) <- group_vars[1]
    diag(S_signal_B) <- group_vars[2]
    
    set.seed(seed+1)
    signal_A <- mvrnorm(n = N/2, Sigma = S_signal_A, mu = rep(0, p_signal)) %>% 
      as.data.frame()
   
    signal_B <- mvrnorm(n = N/2, Sigma = S_signal_B, mu = rep(0, p_signal)) %>% 
      as.data.frame()
    data_signal <- bind_rows(signal_A, signal_B)
    colnames(data_signal) <- paste0("signal_", 1:p_signal)
  } else {data_signal <- list()}
  
  # make discriminating data
  if(p_disc > 0){
    S_disc <- matrix(rep(cov_disc, p_disc^2), ncol = p_disc)
    
    # make data for group A
    diag(S_disc) <- group_vars[1]
    set.seed(seed+2)
    means_A <- rep((diff_disc/2), p_disc)
    data_A <- mvrnorm(n = N/2, mu = means_A, Sigma = S_disc) %>%
      as.data.frame()
    
    # make data for group B
    diag(S_disc) <- group_vars[2]
    set.seed(seed+3)
    means_B <- rep(-1*(diff_disc/2), p_disc)
    data_B <- mvrnorm(n = N/2, mu = means_B, Sigma = S_disc) %>%
      as.data.frame()
    
    data_disc <- bind_rows(data_A, data_B)
    colnames(data_disc) <- paste0("disc_", 1:p_disc)
  } else{data_disc <- list()}
  
  # column bind it all together
  data1 <- bind_cols(data_disc, data_signal, data_noise) %>%
    add_column(group = rep(c("a","b"), each = N/2), .before = 1)
  
  return(data1)
}

1. Discriminating variables don’t co-vary

Generate data

First, make a bunch of datasets with the same parameters

nperm = 50 #how many perumutations
#generate data
data1.list <- map(1:nperm,
                      ~sim.vcov(p_noise = 10,
                                p_signal = 25,
                                p_disc = 5,
                                cov_signal = 0.8,
                                diff_disc = 1.2,
                                cov_disc = 0.2,
                                N = 30,
                                seed = NA))
# data1.list[[1]]

PERMANOVA

Do PERMANOVA on all of them

PERMANOVAresults1 <- map_dbl(data1.list,
    ~ adonis(select(., -group) ~ .$group, method = "eu")$aov.tab$`Pr(>F)`[1])

PLS-DA

Now do PLS-DA on all of them and get p-values (this will take a long time)

And extract p-values

PLSresults1 <- plsda1.list %>% map_df(~get_plotdata(.)$model_stats)
# PLSresults1

Plot Results

comparison1 <- bind_cols("permanova"= PERMANOVAresults1, PLSresults1)
p1.permanova <- ggplot(comparison1) +
  geom_density(aes(permanova), fill = "green", alpha = 0.33) +
  labs(x = "p value") +
  geom_vline(xintercept = 0.05, linetype = 5) +
  ggtitle("PERMANOVA") +
  xlim(0, 0.6)
# p1.permanova
p1.plsda <- ggplot(comparison1) +
  geom_density(aes(pQ2), fill = "red", alpha = 0.33) +
  labs(x = "p value") +
  geom_vline(xintercept = 0.05, linetype = 5) +
  ggtitle("PLS-DA")
# p1.plsda
p1 <- plot_grid(p1.permanova, p1.plsda)

2. Discriminating variables co-vary

Generate data

First, make a bunch of datasets with the same parameters

# nperm = 100 #how many perumutations
#generate data
data2.list <- map(1:nperm,
                      ~sim.vcov(p_noise = 10,
                                p_signal = 25,
                                p_disc = 5,
                                cov_signal = 0.8,
                                diff_disc = 1.2,
                                cov_disc = 0.6,
                                N = 30,
                                seed = NA))
# data2.list[[1]]

PERMANOVA

Do PERMANOVA on all of them

PERMANOVAresults2 <- map_dbl(data2.list,
    ~ adonis(select(., -group) ~ .$group, method = "eu")$aov.tab$`Pr(>F)`[1])

PLS-DA

Now do PLS-DA on all of them and get p-values (this will take a long time)

And extract p-values

PLSresults2 <- plsda2.list %>% map_df(~get_plotdata(.)$model_stats)
# PLSresults2

Plot Results

comparison2 <- bind_cols("permanova"= PERMANOVAresults2, PLSresults2)
p2.permanova <- ggplot(comparison2) +
  geom_density(aes(permanova), fill = "green", alpha = 0.33) +
  labs(x = "p value") +
  geom_vline(xintercept = 0.05, linetype = 5) +
  ggtitle("PERMANOVA")
# p2.permanova
p2.plsda <- ggplot(comparison2) +
  geom_density(aes(pQ2), fill = "red", alpha = 0.33) +
  labs(x = "p value") +
  geom_vline(xintercept = 0.05, linetype = 5) +
  ggtitle("PLS-DA")
# p2.plsda
p2 <- plot_grid(p2.permanova, p2.plsda)
scenario1 <- plot_grid(p1, p2, ncol = 1, labels = c("cov = 0.2", "cov = 0.6"))
scenario1

PLS-DA performs better than PERMANOVA when there are many co-varying variables not related to response variable. This make sense because it is analagous to ANOVA but uses covariances. So the ratio of the within group covariance to the between group covariance shrinks when there are a bunch of highly correlated variables not related to group membership, yeah?

This was an artifact created by the same seed being used for multiple pieces of the dataset. That made them correlated when they shouldn’t be.

sim.vcov(p_noise = 10,
  p_signal = 25,
  p_disc = 5,
  cov_signal = 0.8,
  diff_disc = 1.2,
  cov_disc = 0.6,
  N = 30,
  seed = NA))
comparison1 %>% 
  summarise_all(mean) %>% bind_rows(
comparison2 %>% 
  summarise_all(mean)
) %>% 
  add_column(cov = c("0.2", "0.7") , .before = 1)
test <- sim.vcov(p_noise = 20,
         p_signal = 10,
         p_disc = 10,
         cov_signal = 0.8,
         diff_disc = 1.2,
         cov_disc = 0.5,
         N = 30,
         seed = 100)
# ggplot(test, aes(x = disc_4, y = disc_5, color = group)) +
#   geom_point() 
library(iheatmapr)
test %>% select(-group) %>% cor() %>% iheatmap(row_labels = TRUE, col_labels = TRUE)
LS0tCnRpdGxlOiAiUExTLURBIHZzIFBFUk1BTk9WQSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKSXQgc2VlbXMgbGlrZSBQRVJNQU5PVkEgaXMgcHJldHR5IGdvb2QgYXQgZG9pbmcgZGlzY3JpbWluYW50IGFuYWx5c2VzLiAgV2hlbiBpcyBQTFMtREEgYmV0dGVyLCBpZiBldmVyPwoKI1RvLURvOgogLSB+flRyeSBtb2RpZnlpbmcgZnVuY3Rpb24gdG8gYWxsb3cgZm9yIGRpZmZlcmVudCB2YXJpYW5jZXMgYmV0d2VlbiBncm91cHMuICBQRVJNQU5PVkEgaXMgbm90IHJvYnVzdCB0byBkaWZmZXJlbmNlcyBpbiB2YXJpYW5jZXMgYmV0d2VlbiBncm91cHMuICBTZWUgaG93IFBMUy1EQSBkb2VzLn5+CiAqTG9va3MgbGlrZSBib3RoIG1ldGhvZHMgYXJlIHNlbnNpdGl2ZSB0byBkZXBhcnR1cmVzIGZyb20gaG9tb2dlbmlldHkgb2YgdmFyaWFuY2VzLioKCiAtIFRlc3QgUExTLURBIHZzLiBQRVJNQU5PVkEgd2l0aCB0aGUgZGlzY3JpbWluYXRpbmcgdmFyaWFibGVzIGNvLXZhcnlpbmcgYW5kIG5vdCBjby12YXJ5aW5nLiBQRVJNQU5PVkEgc2hvdWRsIGJlIGFib3V0IHRoZSBzYW1lLCBidXQgUExTLURBIHNob3VsZCBiZSBkaWZmZXJlbnQgYmVjYXVzZSBpdCBhY2NvdW50cyBmb3IgY28tdmFyeWF0aW9uLCByaWdodD8gIENoZWNrIFJeMlggdmFsdWVzCiAKIC0gRG8gdGhlIGFib3ZlIHRlc3Qgd2l0aCBhIGRpZmZlcmVudCBtZXRob2Qgb2YgZ2VuZXJhdGluZyBkaXNjcmltaW5hdGluZyB2YXJpYWJsZXMuCiAKIC0gTG9vayB1cCBEREEgcG9zdC1ob2MgdGVzdCBmb3IgcGVybWFub3ZhLgogCiAtICpEbyBzYW1lIGV4YWN0IHRlc3QgYnV0IHdpdGggYGNoZW1oZWxwZXI6OnNpbV9tdWx0dmFyYCB0byBmaWd1cmUgb3V0IHdoYXQgaXMgd3Jvbmcgd2l0aCBpdC4KCiMgU2V0dXAKIyMgTG9hZCBwYWNrYWdlcwpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIFJlcXVpcmVkIFBhY2thZ2VzCmxpYnJhcnkoTUFTUykKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocm9wbHMpCmxpYnJhcnkoY2hlbWhlbHBlcikKI2NoZW1oZWxwZXIgY29udGFpbnMgY3VzdG9tIGZ1bmN0aW9ucyBmb3IgaW50ZXJmYWNpbmcgd2l0aCByb3BscyBwYWNrYWdlIGluIGEgZnJpZW5kbGllciB3YXkgKGFuZCBvdGhlciB0aGluZ3MpLiAgCiNJbnN0YWxsIHdpdGggZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJBYXJpcS9jaGVtaGVscGVyIikKbGlicmFyeShjb3dwbG90KSAjZm9yIG1ha2luZyBhbmQgc2F2aW5nIHByZXR0aWVyIHBsb3RzCmxpYnJhcnkoYnJvb20pICNmb3IgdGlkeSgpLCB3aGljaCB0dXJucyBtb2RlbCBvdXRwdXQgaW50byBkYXRhZnJhbWVzCmxpYnJhcnkodmVnYW4pICNmb3IgUEVSTUFOT1ZBCmBgYAoKIyMgQ3JlYXRlIEZ1bmN0aW9uIHRvIEdlbmVyYXRlIERhdGEKVGhpcyBjb2RlIGNodW5rIGNyZWF0ZXMgdGhlIGZ1bmN0aW9uIGBzaW0udmNvdigpYCB3aGljaCBnZW5lcmF0ZXMgYSBkYXRhc2V0IHdoZXJlIHdlIGNhbiB0dW5lIHNpZ25hbCwgbm9pc2UsIGRpc2NyaW1pbmF0aW5nIHZhcmlhYmxlcywgc3RyZW5ndGggb2Ygc2lnbmFsLCBhbmQgc3RyZW5ndGggb2YgZGlzY3JpbWluYXRpbmcgdmFyaWFibGVzLiBJJ20gdXNpbmcgdGhlIG5vdGF0aW9uIGBuYCBmb3IgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBhbmQgYHBgIGZvciBudW1iZXIgb2YgdmFyaWFibGVzLiBgc2ltLnZjb3YoKWAgcmVxdWlyZXMgdGhlIGZvbGxvd2luZyBhcmd1bWVudHM6CgotIGBwX25vaXNlYDogaG93IG1hbnkgdmFyaWFibGVzIHdpdGggY292YXJpYW5jZSA9IDAuMDE/Ci0gYHBfc2lnbmFsYDogaG93IG1hbnkgdmFyaWFibGVzIHdpdGggc29tZSBjb3ZhcmlhbmNlIChjb3ZhcmlhbmNlID0gYGNvdl9zaWduYWxgKT8KLSBgcF9kaXNjYDogaG93IG1hbnkgZGlzY3JpbWluYXRpbmcgdmFyaWFibGVzPwotIGBjb3Zfc2lnbmFsYDogdGhlIGNvdmFyaWFuY2UgZm9yIHRoZSB2YXJpYWJsZXMgd2l0aCBub24temVybyBjb3ZhcmlhbmNlLgotIGBkaWZmX2Rpc2NgOiB0aGUgZGlmZmVyZW5jZSBpbiBtZWFucyBmb3IgdGhlIGRpc2NyaW1pbmF0aW5nIHZhcmlhYmxlcyBiZXR3ZWVuIHRoZSB0d28gZ3JvdXBzLgotIGBOYDogaG93IG1hbnkgb2JzZXJ2YXRpb25zPwotIGBzZWVkYDogcGFzc2VkIHRvIGBzZXQuc2VlZCgpYAoKYGBge3J9CiMgcF9ub2lzZSA9IDAKIyBwX3NpZ25hbCA9IDUKIyBwX2Rpc2MgPSAxNQojIGNvdl9zaWduYWwgPSAwLjUKIyBkaWZmX2Rpc2MgPSAxCiMgY292X2Rpc2MgPSAwLjUKIyBncm91cF92YXJzID0gYygxLDIpCiMgTiA9IDIwCiMgc2VlZCA9IE5BCgpzaW0udmNvdiA8LSBmdW5jdGlvbihwX25vaXNlLAogICAgICAgICAgICAgICAgICAgICBwX3NpZ25hbCwKICAgICAgICAgICAgICAgICAgICAgcF9kaXNjLAogICAgICAgICAgICAgICAgICAgICBjb3Zfc2lnbmFsLAogICAgICAgICAgICAgICAgICAgICBjb3ZfZGlzYywKICAgICAgICAgICAgICAgICAgICAgZGlmZl9kaXNjLAogICAgICAgICAgICAgICAgICAgICBncm91cF92YXJzID0gYygxLCAxKSwKICAgICAgICAgICAgICAgICAgICAgTiwKICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IE5BKXsKICAjIENob29zZSBhIHJhbmRvbSBudW1iZXIgZm9yIHNlZWQgaWYgbm9uZSBpcyBzdXBwbGllZAogIGlmKGlzLm5hKHNlZWQpKXsKICAgIHNlZWQgPSBhcy5pbnRlZ2VyKHJ1bmlmKDEpICogMTBlNikKICB9CiAgCiAgIyBtYWtlIG5vaXNlIGRhdGEKICBpZihwX25vaXNlID4gMCl7CiAgU19ub2lzZSA8LSBtYXRyaXgocmVwKDAuMDAxLCBwX25vaXNlXjIpLCBuY29sID0gcF9ub2lzZSkKICBkaWFnKFNfbm9pc2UpIDwtIDEKICBzZXQuc2VlZChzZWVkKQogIGRhdGFfbm9pc2UgPC0gbXZybm9ybShuID0gTiwgU2lnbWEgPSBTX25vaXNlLCBtdSA9IHJlcCgwLCBwX25vaXNlKSkgJT4lCiAgICBhcy5kYXRhLmZyYW1lKCkKICBjb2xuYW1lcyhkYXRhX25vaXNlKSA8LSBwYXN0ZTAoIm5vaXNlXyIsIDE6cF9ub2lzZSkKICB9IGVsc2Uge2RhdGFfbm9pc2UgPC0gbGlzdCgpfQogIAogICMgbWFrZSBzaWduYWwgZGF0YQogIGlmKHBfc2lnbmFsID4gMCl7CiAgICBTX3NpZ25hbCA8LSBtYXRyaXgocmVwKGNvdl9zaWduYWwsIHBfc2lnbmFsXjIpLCBuY29sID0gcF9zaWduYWwpCiAgICBTX3NpZ25hbF9CIDwtIFNfc2lnbmFsX0EgPC0gU19zaWduYWwKICAgIGRpYWcoU19zaWduYWxfQSkgPC0gZ3JvdXBfdmFyc1sxXQogICAgZGlhZyhTX3NpZ25hbF9CKSA8LSBncm91cF92YXJzWzJdCiAgICAKICAgIHNldC5zZWVkKHNlZWQrMSkKICAgIHNpZ25hbF9BIDwtIG12cm5vcm0obiA9IE4vMiwgU2lnbWEgPSBTX3NpZ25hbF9BLCBtdSA9IHJlcCgwLCBwX3NpZ25hbCkpICU+JSAKICAgICAgYXMuZGF0YS5mcmFtZSgpCiAgICBzaWduYWxfQiA8LSBtdnJub3JtKG4gPSBOLzIsIFNpZ21hID0gU19zaWduYWxfQiwgbXUgPSByZXAoMCwgcF9zaWduYWwpKSAlPiUgCiAgICAgIGFzLmRhdGEuZnJhbWUoKQogICAgCiAgICBkYXRhX3NpZ25hbCA8LSBiaW5kX3Jvd3Moc2lnbmFsX0EsIHNpZ25hbF9CKQogICAgY29sbmFtZXMoZGF0YV9zaWduYWwpIDwtIHBhc3RlMCgic2lnbmFsXyIsIDE6cF9zaWduYWwpCiAgfSBlbHNlIHtkYXRhX3NpZ25hbCA8LSBsaXN0KCl9CiAgCiAgIyBtYWtlIGRpc2NyaW1pbmF0aW5nIGRhdGEKICBpZihwX2Rpc2MgPiAwKXsKICAgIFNfZGlzYyA8LSBtYXRyaXgocmVwKGNvdl9kaXNjLCBwX2Rpc2NeMiksIG5jb2wgPSBwX2Rpc2MpCiAgICAKICAgICMgbWFrZSBkYXRhIGZvciBncm91cCBBCiAgICBkaWFnKFNfZGlzYykgPC0gZ3JvdXBfdmFyc1sxXQogICAgc2V0LnNlZWQoc2VlZCsyKQogICAgbWVhbnNfQSA8LSByZXAoKGRpZmZfZGlzYy8yKSwgcF9kaXNjKQogICAgZGF0YV9BIDwtIG12cm5vcm0obiA9IE4vMiwgbXUgPSBtZWFuc19BLCBTaWdtYSA9IFNfZGlzYykgJT4lCiAgICAgIGFzLmRhdGEuZnJhbWUoKQogICAgCiAgICAjIG1ha2UgZGF0YSBmb3IgZ3JvdXAgQgogICAgZGlhZyhTX2Rpc2MpIDwtIGdyb3VwX3ZhcnNbMl0KICAgIHNldC5zZWVkKHNlZWQrMykKICAgIG1lYW5zX0IgPC0gcmVwKC0xKihkaWZmX2Rpc2MvMiksIHBfZGlzYykKICAgIGRhdGFfQiA8LSBtdnJub3JtKG4gPSBOLzIsIG11ID0gbWVhbnNfQiwgU2lnbWEgPSBTX2Rpc2MpICU+JQogICAgICBhcy5kYXRhLmZyYW1lKCkKICAgIAogICAgZGF0YV9kaXNjIDwtIGJpbmRfcm93cyhkYXRhX0EsIGRhdGFfQikKICAgIGNvbG5hbWVzKGRhdGFfZGlzYykgPC0gcGFzdGUwKCJkaXNjXyIsIDE6cF9kaXNjKQogIH0gZWxzZXtkYXRhX2Rpc2MgPC0gbGlzdCgpfQogIAogICMgY29sdW1uIGJpbmQgaXQgYWxsIHRvZ2V0aGVyCiAgZGF0YTEgPC0gYmluZF9jb2xzKGRhdGFfZGlzYywgZGF0YV9zaWduYWwsIGRhdGFfbm9pc2UpICU+JQogICAgYWRkX2NvbHVtbihncm91cCA9IHJlcChjKCJhIiwiYiIpLCBlYWNoID0gTi8yKSwgLmJlZm9yZSA9IDEpCiAgCiAgcmV0dXJuKGRhdGExKQp9CmBgYAoKIyAxLiBEaXNjcmltaW5hdGluZyB2YXJpYWJsZXMgZG9uJ3QgY28tdmFyeQojIyBHZW5lcmF0ZSBkYXRhCkZpcnN0LCBtYWtlIGEgYnVuY2ggb2YgZGF0YXNldHMgd2l0aCB0aGUgc2FtZSBwYXJhbWV0ZXJzCmBgYHtyfQpucGVybSA9IDUwICNob3cgbWFueSBwZXJ1bXV0YXRpb25zCgojZ2VuZXJhdGUgZGF0YQpkYXRhMS5saXN0IDwtIG1hcCgxOm5wZXJtLAogICAgICAgICAgICAgICAgICAgICAgfnNpbS52Y292KHBfbm9pc2UgPSAxMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwX3NpZ25hbCA9IDI1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBfZGlzYyA9IDUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY292X3NpZ25hbCA9IDAuOCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaWZmX2Rpc2MgPSAxLjIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY292X2Rpc2MgPSAwLjIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTiA9IDMwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSBOQSkpCiMgZGF0YTEubGlzdFtbMV1dCmBgYAoKIyMgUEVSTUFOT1ZBCkRvIFBFUk1BTk9WQSBvbiBhbGwgb2YgdGhlbQpgYGB7cn0KUEVSTUFOT1ZBcmVzdWx0czEgPC0gbWFwX2RibChkYXRhMS5saXN0LAogICAgfiBhZG9uaXMoc2VsZWN0KC4sIC1ncm91cCkgfiAuJGdyb3VwLCBtZXRob2QgPSAiZXUiKSRhb3YudGFiJGBQcig+RilgWzFdKQpgYGAKCiMjIFBMUy1EQQpOb3cgZG8gUExTLURBIG9uIGFsbCBvZiB0aGVtIGFuZCBnZXQgcC12YWx1ZXMgKHRoaXMgd2lsbCB0YWtlIGEgbG9uZyB0aW1lKQpgYGB7ciBpbmNsdWRlPUZBTFNFfQpwbHNkYTEubGlzdCA8LSBtYXAoZGF0YTEubGlzdCwgfm9wbHMoc2VsZWN0KC4sIC1ncm91cCksIC4kZ3JvdXAsIHByZWRJID0gMiwgcGVybUkgPSAyMDAsIHBsb3RMID0gRkFMU0UpKQpgYGAKCkFuZCBleHRyYWN0IHAtdmFsdWVzCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9ClBMU3Jlc3VsdHMxIDwtIHBsc2RhMS5saXN0ICU+JSBtYXBfZGYofmdldF9wbG90ZGF0YSguKSRtb2RlbF9zdGF0cykKIyBQTFNyZXN1bHRzMQpgYGAKCiMjIFBsb3QgUmVzdWx0cwpgYGB7cn0KY29tcGFyaXNvbjEgPC0gYmluZF9jb2xzKCJwZXJtYW5vdmEiPSBQRVJNQU5PVkFyZXN1bHRzMSwgUExTcmVzdWx0czEpCgpwMS5wZXJtYW5vdmEgPC0gZ2dwbG90KGNvbXBhcmlzb24xKSArCiAgZ2VvbV9kZW5zaXR5KGFlcyhwZXJtYW5vdmEpLCBmaWxsID0gImdyZWVuIiwgYWxwaGEgPSAwLjMzKSArCiAgbGFicyh4ID0gInAgdmFsdWUiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMC4wNSwgbGluZXR5cGUgPSA1KSArCiAgZ2d0aXRsZSgiUEVSTUFOT1ZBIikgKwogIHhsaW0oMCwgMC42KQojIHAxLnBlcm1hbm92YQoKcDEucGxzZGEgPC0gZ2dwbG90KGNvbXBhcmlzb24xKSArCiAgZ2VvbV9kZW5zaXR5KGFlcyhwUTIpLCBmaWxsID0gInJlZCIsIGFscGhhID0gMC4zMykgKwogIGxhYnMoeCA9ICJwIHZhbHVlIikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAuMDUsIGxpbmV0eXBlID0gNSkgKwogIGdndGl0bGUoIlBMUy1EQSIpCiMgcDEucGxzZGEKCnAxIDwtIHBsb3RfZ3JpZChwMS5wZXJtYW5vdmEsIHAxLnBsc2RhKQpgYGAKCiMgMi4gRGlzY3JpbWluYXRpbmcgdmFyaWFibGVzIGNvLXZhcnkKIyMgR2VuZXJhdGUgZGF0YQpGaXJzdCwgbWFrZSBhIGJ1bmNoIG9mIGRhdGFzZXRzIHdpdGggdGhlIHNhbWUgcGFyYW1ldGVycwpgYGB7cn0KIyBucGVybSA9IDEwMCAjaG93IG1hbnkgcGVydW11dGF0aW9ucwoKI2dlbmVyYXRlIGRhdGEKZGF0YTIubGlzdCA8LSBtYXAoMTpucGVybSwKICAgICAgICAgICAgICAgICAgICAgIH5zaW0udmNvdihwX25vaXNlID0gMTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcF9zaWduYWwgPSAyNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwX2Rpc2MgPSA1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvdl9zaWduYWwgPSAwLjgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlmZl9kaXNjID0gMS4yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvdl9kaXNjID0gMC42LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE4gPSAzMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gTkEpKQojIGRhdGEyLmxpc3RbWzFdXQpgYGAKCiMjIFBFUk1BTk9WQQpEbyBQRVJNQU5PVkEgb24gYWxsIG9mIHRoZW0KYGBge3J9ClBFUk1BTk9WQXJlc3VsdHMyIDwtIG1hcF9kYmwoZGF0YTIubGlzdCwKICAgIH4gYWRvbmlzKHNlbGVjdCguLCAtZ3JvdXApIH4gLiRncm91cCwgbWV0aG9kID0gImV1IikkYW92LnRhYiRgUHIoPkYpYFsxXSkKYGBgCgojIyBQTFMtREEKTm93IGRvIFBMUy1EQSBvbiBhbGwgb2YgdGhlbSBhbmQgZ2V0IHAtdmFsdWVzICh0aGlzIHdpbGwgdGFrZSBhIGxvbmcgdGltZSkKYGBge3IgaW5jbHVkZT1GQUxTRX0KcGxzZGEyLmxpc3QgPC0gbWFwKGRhdGEyLmxpc3QsIH5vcGxzKHNlbGVjdCguLCAtZ3JvdXApLCAuJGdyb3VwLCBwcmVkSSA9IDIsIHBlcm1JID0gMjAwLCBwbG90TCA9IEZBTFNFKSkKYGBgCgpBbmQgZXh0cmFjdCBwLXZhbHVlcwpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpQTFNyZXN1bHRzMiA8LSBwbHNkYTIubGlzdCAlPiUgbWFwX2RmKH5nZXRfcGxvdGRhdGEoLikkbW9kZWxfc3RhdHMpCiMgUExTcmVzdWx0czIKYGBgCgojIyBQbG90IFJlc3VsdHMKYGBge3J9CmNvbXBhcmlzb24yIDwtIGJpbmRfY29scygicGVybWFub3ZhIj0gUEVSTUFOT1ZBcmVzdWx0czIsIFBMU3Jlc3VsdHMyKQoKcDIucGVybWFub3ZhIDwtIGdncGxvdChjb21wYXJpc29uMikgKwogIGdlb21fZGVuc2l0eShhZXMocGVybWFub3ZhKSwgZmlsbCA9ICJncmVlbiIsIGFscGhhID0gMC4zMykgKwogIGxhYnMoeCA9ICJwIHZhbHVlIikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAuMDUsIGxpbmV0eXBlID0gNSkgKwogIGdndGl0bGUoIlBFUk1BTk9WQSIpCiMgcDIucGVybWFub3ZhCgpwMi5wbHNkYSA8LSBnZ3Bsb3QoY29tcGFyaXNvbjIpICsKICBnZW9tX2RlbnNpdHkoYWVzKHBRMiksIGZpbGwgPSAicmVkIiwgYWxwaGEgPSAwLjMzKSArCiAgbGFicyh4ID0gInAgdmFsdWUiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMC4wNSwgbGluZXR5cGUgPSA1KSArCiAgZ2d0aXRsZSgiUExTLURBIikKIyBwMi5wbHNkYQoKcDIgPC0gcGxvdF9ncmlkKHAyLnBlcm1hbm92YSwgcDIucGxzZGEpCmBgYAoKYGBge3J9CnNjZW5hcmlvMSA8LSBwbG90X2dyaWQocDEsIHAyLCBuY29sID0gMSwgbGFiZWxzID0gYygiY292ID0gMC4yIiwgImNvdiA9IDAuNiIpKQpzY2VuYXJpbzEKYGBgCgp+flBMUy1EQSBwZXJmb3JtcyBiZXR0ZXIgdGhhbiBQRVJNQU5PVkEgd2hlbiB0aGVyZSBhcmUgbWFueSBjby12YXJ5aW5nIHZhcmlhYmxlcyBub3QgcmVsYXRlZCB0byByZXNwb25zZSB2YXJpYWJsZS4gIFRoaXMgbWFrZSBzZW5zZSBiZWNhdXNlIGl0IGlzIGFuYWxhZ291cyB0byBBTk9WQSBidXQgdXNlcyBjb3ZhcmlhbmNlcy4gIFNvIHRoZSByYXRpbyBvZiB0aGUgd2l0aGluIGdyb3VwIGNvdmFyaWFuY2UgdG8gdGhlIGJldHdlZW4gZ3JvdXAgY292YXJpYW5jZSBzaHJpbmtzIHdoZW4gdGhlcmUgYXJlIGEgYnVuY2ggb2YgaGlnaGx5IGNvcnJlbGF0ZWQgdmFyaWFibGVzIG5vdCByZWxhdGVkIHRvIGdyb3VwIG1lbWJlcnNoaXAsIHllYWg/fn4KClRoaXMgd2FzIGFuIGFydGlmYWN0IGNyZWF0ZWQgYnkgdGhlIHNhbWUgc2VlZCBiZWluZyB1c2VkIGZvciBtdWx0aXBsZSBwaWVjZXMgb2YgdGhlIGRhdGFzZXQuICBUaGF0IG1hZGUgdGhlbSBjb3JyZWxhdGVkIHdoZW4gdGhleSBzaG91bGRuJ3QgYmUuCmBgYApzaW0udmNvdihwX25vaXNlID0gMTAsCiAgcF9zaWduYWwgPSAyNSwKICBwX2Rpc2MgPSA1LAogIGNvdl9zaWduYWwgPSAwLjgsCiAgZGlmZl9kaXNjID0gMS4yLAogIGNvdl9kaXNjID0gMC42LAogIE4gPSAzMCwKICBzZWVkID0gTkEpKQpgYGAKCgpgYGB7cn0KY29tcGFyaXNvbjEgJT4lIAogIHN1bW1hcmlzZV9hbGwobWVhbikgJT4lIGJpbmRfcm93cygKY29tcGFyaXNvbjIgJT4lIAogIHN1bW1hcmlzZV9hbGwobWVhbikKKSAlPiUgCiAgYWRkX2NvbHVtbihjb3YgPSBjKCIwLjIiLCAiMC43IikgLCAuYmVmb3JlID0gMSkKYGBgCmBgYHtyfQp0ZXN0IDwtIHNpbS52Y292KHBfbm9pc2UgPSAyMCwKICAgICAgICAgcF9zaWduYWwgPSAxMCwKICAgICAgICAgcF9kaXNjID0gMTAsCiAgICAgICAgIGNvdl9zaWduYWwgPSAwLjgsCiAgICAgICAgIGRpZmZfZGlzYyA9IDEuMiwKICAgICAgICAgY292X2Rpc2MgPSAwLjUsCiAgICAgICAgIE4gPSAzMCwKICAgICAgICAgc2VlZCA9IDEwMCkKCiMgZ2dwbG90KHRlc3QsIGFlcyh4ID0gZGlzY180LCB5ID0gZGlzY181LCBjb2xvciA9IGdyb3VwKSkgKwojICAgZ2VvbV9wb2ludCgpIApsaWJyYXJ5KGloZWF0bWFwcikKdGVzdCAlPiUgc2VsZWN0KC1ncm91cCkgJT4lIGNvcigpICU+JSBpaGVhdG1hcChyb3dfbGFiZWxzID0gVFJVRSwgY29sX2xhYmVscyA9IFRSVUUpCmBgYApgYGB7cn0KdGVzdDIgPC0gc2ltX211bHR2YXIocF91bmNvcnZhciA9IDIwLAogICAgICAgICAgICAgICAgICAgICBwX2NvcnZhciA9IDEwLAogICAgICAgICAgICAgICAgICAgICBwX2Rpc2NyID0gMTAsCiAgICAgICAgICAgICAgICAgICAgIGNvdl9jb3J2YXIgPSAwLjgsCiAgICAgICAgICAgICAgICAgICAgIGRpZmZfZGlzY3IgPSAxLjIsCiAgICAgICAgICAgICAgICAgICAgIGNvdl9kaXNjciA9IDAuNSwKICAgICAgICAgICAgICAgICAgICAgTiA9IDMwLAogICAgICAgICAgICAgICAgICAgICBzZWVkID0gMTAwKQoKdGVzdDIgJT4lIHNlbGVjdCgtZ3JvdXApICU+JSBjb3IoKSAlPiUgaWhlYXRtYXAocm93X2xhYmVscyA9IFRSVUUsIGNvbF9sYWJlbHMgPSBUUlVFKQoKdGVzdDIKYGBgCgo=